#!/usr/bin/python # btpeer.py 
import socket 
import struct 
import threading 
import time 
import traceback 
from distributed_kmeans import kmeans_node

def btdebug( msg ): 
    """ Prints a messsage to the screen with the name of the current thread """ 
    print "[%s] %s" % ( str(threading.currentThread().getName()), msg ) 

#============================================================================== 
class Node_failed(Exception):
       def __init__(self, value):
           self.parameter = value
       def __str__(self):
           return repr(self.parameter)
       
class BTPeer: 
      """ Implements the core functionality that might be used by a peer in a P2P network. """
#-------------------------------------------------------------------------- 
      def __init__( self, maxpeers, serverport, myid=None, serverhost = None ): 
#-------------------------------------------------------------------------- 
          """ Initializes a peer servent (sic.) with the ability to catalog information for up to maxpeers number of peers (maxpeers may be set to 0 to allow unlimited number of peers), listening on a given server port , with a given canonical peer name (id) and host address. If not supplied, the host address (serverhost) will be determined by attempting to connect to an Internet host like Google. """ 
          #print "ETPeer has been created"
          self.debug = 1 
          self.maxpeers = int(maxpeers) 
          self.serverport = int(serverport) 
          if serverhost: self.serverhost = serverhost 
          else: self.__initserverhost() 
          if myid: self.myid = myid 
          else: self.myid = '%s:%d' % (self.serverhost, self.serverport) 
          self.peerlock = threading.Lock() # ensure proper access to # peers list (maybe better to use # threading.RLock (reentrant)) self.peers = {} # peerid ==> (host, port) mapping self.shutdown = False # used to stop the main loop 
          self.shutdown=False
          self.handlers = {} 
          self.router = None 
          self.kmeans_node=None

#-------------------------------------------------------------------------- 
      def __initserverhost( self ): 
#-------------------------------------------------------------------------- 
          """ Attempt to connect to an Internet host in order to determine the local machine's IP address. """ 

          s = socket.socket( socket.AF_INET, socket.SOCK_STREAM ) 
          s.connect( ( "www.google.com", 80 ) ) 
          self.serverhost = s.getsockname()[0] 
          s.close() 


      def __debug( self, msg ): 
#-------------------------------------------------------------------------- 
          if self.debug: 
             btdebug( msg )

      def makeserversocket( self, port, backlog=5 ): 
#-------------------------------------------------------------------------- 
          """ Constructs and prepares a server socket listening on the given port. """ 
          s = socket.socket( socket.AF_INET, socket.SOCK_STREAM ) 
          s.setsockopt( socket.SOL_SOCKET, socket.SO_REUSEADDR, 1 ) 
          s.bind( ( '', port ) ) 
          print "i am binding to the port",
          print port
          s.listen( backlog ) 
          return s 

      def __handlepeer( self, BTconnection, Msgtype, Msg ): 
#-------------------------------------------------------------------------- 
          """ handlepeer( new socket connection ) -> () Dispatches messages from the socket connection """ 
          #self.__debug( 'New child ' + str(threading.currentThread().getName()) ) 
          #self.__debug( 'Connected ' + str(clientsock.getpeername()) ) 
          
          #host, port = clientsock.getpeername() 
          #peerconn = BTPeerConnection( None, host, port, clientsock, debug=False )
          peerconn=BTconnection
          try: 
              msgtype=Msgtype
              msgdata=Msg
              if msgtype: 
                 msgtype = msgtype.upper() 
              if msgtype not in self.handlers: 
                 print self.handlers
                 #self.__debug( 'Not handled: %s: %s' % (msgtype, msgdata) ) 
              else: 
                 #self.__debug( 'Handling peer msg: %s: %s' % (msgtype, msgdata) ) 
                 self.handlers[ msgtype ]( peerconn, msgdata ) 
          except KeyboardInterrupt: 
              raise 
          except: 
              if self.debug: 
                 traceback.print_exc() 
          #self.__debug( 'Disconnecting ' + str(clientsock.getpeername()) ) 
          #peerconn.close()

#-------------------------------------------------------------------------- 
      def addhandler( self, msgtype, handler ): 
#-------------------------------------------------------------------------- 
          """ Registers the handler for the given message type with this peer """
          assert len(msgtype) == 4 
          self.handlers[ msgtype ] = handler 

      def connectandsend( self, host, port, msgtype, msgdata, pid=None, waitreply=True ): #-------------------------------------------------------------------------- 
          """ connectandsend( host, port, message type, message data, peer id, wait for a reply ) -> [ ( reply type, reply data ), ... ] Connects and sends a message to the specified host:port. The host's reply, if expected, will be returned as a list of tuples. """ 
          msgreply = [] 
          try: 
              peerconn = BTPeerConnection( pid, host, port, debug=self.debug ) 
              peerconn.senddata( msgtype, msgdata ) 
              self.__debug( 'Sent %s: %s' % (pid, msgtype) ) 
              if waitreply: 
                  onereply = peerconn.recvdata() 
                  while (onereply != (None,None)): 
                      msgreply.append(onereply) 
                      msgtype=onereply[0]
                      msgdata=onereply[1]
                      if msgtype: 
                         msgtype = msgtype.upper() 
                      if msgtype not in self.handlers: 
                         print self.handlers
                         #self.__debug( 'Not handled: %s: %s' % (msgtype, msgdata) ) 
                      else: 
                         #self.__debug( 'Handling peer msg: %s: %s' % (msgtype, msgdata) ) 
                         self.handlers[ msgtype ]( peerconn, msgdata ) 
                      onereply = peerconn.recvdata()
                  self.__debug( 'Got reply %s: %s' % ( pid, str(msgreply) ) ) 
              peerconn.close() 
          except KeyboardInterrupt: 
              ##raise
              pass 
          except: 
              raise Node_failed('this peer is down')
              '''if self.debug: 
                 traceback.print_exc()'''
              pass
          return msgreply # end connectsend method 
#-------------------------------------------------------------------------- 


      def mainloop( self, BTconnection ): 
#-------------------------------------------------------------------------- 
          '''self.__debug("trying debug")
          print self.serverport
          s = self.makeserversocket( self.serverport ) 
          s.settimeout(None) 
          self.__debug( 'Server started: %s (%s:%d)' % ( self.myid, self.serverhost, self.serverport ) ) 
          while not self.shutdown: 
                try: 
                   self.__debug( 'Listening for connections...' ) 
                   clientsock, clientaddr = s.accept() 
                   clientsock.settimeout(None) #blocking mode
                   t = threading.Thread( target = self.__handlepeer, args = [ clientsock ] ) 
                   t.start() 
                except KeyboardInterrupt: 
                   print 'KeyboardInterrupt: stopping mainloop' 
                   self.shutdown = True 
                   continue 
                except: 
                   if self.debug: 
                      traceback.print_exc() 
                      continue 
# end while loop 
          self.__debug( 'Main loop exiting' )
          s.close()'''
          conn=BTconnection
          while True:
              msgtype, msg = conn.recvdata()
              if msg !=None:
                  t = threading.Thread( target = self.__handlepeer, args = [ conn, msgtype, msg ] ) 
                  t.start() 
                  



class BTPeerConnection: 

#-------------------------------------------------------------------------- 
      def __init__( self, peerid, host, port, sock=None, debug=False ): 
#-------------------------------------------------------------------------- 
# any exceptions thrown upwards 
          self.id = peerid 
          self.debug = debug 
          if not sock: 
             self.s = socket.socket( socket.AF_INET, socket.SOCK_STREAM ) 
             self.s.connect( ( host, int(port) ) )
              
          else: 
             self.s = sock 
          self.sd = self.s.makefile( 'rw', 0 ) 

      def __debug( self, msg ): 
#-------------------------------------------------------------------------- 
          if self.debug: btdebug( msg ) 
#-------------------------------------------------------------------------- 

      def senddata( self, msgtype, msgdata ): 
#-------------------------------------------------------------------------- 
          """ senddata( message type, message data ) -> boolean status Send a message through a peer connection. Returns True on success or False if there was an error. """ 
          try: 
              msg = self.__makemsg( msgtype, msgdata )
              self.sd.write( msg ) 
              self.sd.flush() 
              self.__debug( 'Data Sent: %s: %s' % (msgtype, msgdata) )
          except KeyboardInterrupt: 
              raise 
          except: 
              raise Node_failed('this peer is down')
              #traceback.print_exc() 
              return False 
          return True 

#-------------------------------------------------------------------------- 
      def __makemsg( self, msgtype, msgdata ): 
#-------------------------------------------------------------------------- 
          msglen = len(msgdata) 
          msg = struct.pack( "!4sL%ds" % msglen, msgtype, msglen, msgdata ) 
          return msg

      def recvdata( self ): 
#-------------------------------------------------------------------------- 
          """ recvdata() -> (msgtype, msgdata) Receive a message from a peer connection. Returns (None, None) if there was any error. """ 
          try: 
               #print 'inside recvdata'
               msgtype = self.sd.read( 4 ) 
               #print msgtype
               if not msgtype: 
                  return (None, None) 
               lenstr = self.sd.read( 4 ) 
               msglen = int(struct.unpack( "!L", lenstr )[0])  # the first byte of these four bytes indicate the length of the message
               #print msglen
               msg = "" 
               while len(msg) != msglen: 
                     data = self.sd.read( min(2048, msglen - len(msg)) ) 
                     if not len(data): 
                        break 
                     msg += data 
               if len(msg) != msglen: 
                  return (None, None) 
               self.__debug( 'Data Received: %s: %s' % (msgtype, msg) )
          except KeyboardInterrupt: 
      
               raise 
          except: 
               if self.debug: 
                  traceback.print_exc() 
                  return (None, None) 
          return ( msgtype, msg ) 
# end recvdata method 

#-------------------------------------------------------------------------- 
      def close( self ): 
#-------------------------------------------------------------------------- 
          """ close() Close the peer connection. The send and recv methods will not work after this call. """ 
          self.s.close() 
          self.s = None 
          self.sd = None 



